package com.yunmasoft.service.pay.wexin;


import com.common.bean.exception.LogicBusinessException;
import com.yunmasoft.service.pay.wexin.exception.WxpayException;
import com.yunmasoft.service.pay.wexin.exception.WxpayProtocolException;
import com.yunmasoft.service.pay.wexin.generic.WxpayBase;
import com.house.ujiayigou.thirdpart.weixin.XMLParser;
import com.io.hw.json.HWJacksonUtils;
import org.apache.log4j.Logger;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * General response passed by req.execute() or alipay callback/redirect gateways
 * Actually reqs/gateways passed excatly response type, i.e. sub-class of this.
 * I'm recommending you to see the sub-class javadoc... to help you write elegant code.
 * Notice that downloadbill's response does not inherit this class. That's ass hole.
 */
public class WxpayResponseBase extends WxpayBase {
    protected static final Logger logger = Logger.getLogger(WxpayResponseBase.class);


    protected Boolean validity = null;

    // STRING
    /**
     * @deprecated since 0.4.5 WxpayResponse no longer preserve the origin response body
     */
    protected String respString;

    // CONSTRUCT
    public WxpayResponseBase() {
        return;
    }


    /**
     * This construstor automatically parse input as xml, and output properties. Meanwhile, detect the fails.
     * Notice that Properties does not support hierachy, so it go down if tag names are non-unique.
     * It is raw in present. If it really happens, a new response type and parser should be defined to cope with that.
     */
    public WxpayResponseBase(String xml)
            throws WxpayProtocolException, WxpayException {
        this.respString = xml;
        this.respProp = XMLParser.parseXML(xml);

        if (!this.isReturnCodeSuccess())
            throw (
                    new WxpayProtocolException(
                            this.respProp.getProperty(KEY_RETURN_MSG)
                    ));

        if (!this.isResultCodeSuccess())
            throw (
                    new WxpayException(
                            this.respProp.getProperty(KEY_ERR_CODE)
                    ));

        return;
    }


    /**
     * This construstor automatically parse input as xml, and output properties. Meanwhile, detect the fails.
     * Notice that Properties does not support hierachy, so it go down if tag names are non-unique.
     * It is raw in present. If it really happens, a new response type and parser should be defined to cope with that.
     */
    public WxpayResponseBase(InputStream xml)
            throws IOException {
        this.respProp = XMLParser.parseXML(xml);
        logger.error("WxpayResponseBase parseXML");
        logger.error(this.respProp);
        logger.error(HWJacksonUtils.getJsonP(this.respProp));
        xml.close();
        if (!this.isReturnCodeSuccess())
            throw (
                    new LogicBusinessException("",
                            this.respProp.getProperty(KEY_RETURN_MSG), ""
                    ));

        if (!this.isResultCodeSuccess()) {
            if (null != this.respProp) {
                System.out.println(this.respProp);
                String errorMessage = this.respProp.getProperty("err_code_des");
                System.out.println(errorMessage);
                if ("UNKNOWN".equals(errorMessage)) {
                    System.out.println("请确认订单号是否为空");
                }
            }
            throw (
                    new WxpayException(
                            this.respProp.getProperty(KEY_ERR_CODE)
                    ));
        }

        return;
    }

    /**
     * @deprecated due to it doesn't parse and detect fails. ONLY FOR DEBUGGING.
     */
    public WxpayResponseBase(Properties aRespProp) {
        this(null, aRespProp);

        return;
    }

    /**
     * @deprecated due to it doesn't parse and detect fails. ONLY FOR DEBUGGING.
     */
    public WxpayResponseBase(String aRespString, Properties aRespProp) {
        this.respString = aRespString;
        this.respProp = aRespProp;

        return;
    }

    // UTIL
    protected static Properties buildConf(Properties prop, Properties defaults) {
        Properties ret = new Properties(defaults);
        Iterator<String> iter = prop.stringPropertyNames().iterator();
        while (iter.hasNext()) {
            String key = iter.next();
            ret.setProperty(key, prop.getProperty(key));
        }

        return (ret);
    }

    protected static String materializeParamName(String template, Integer... params) {
        String s = template;

        for (int i = 0; i < params.length; i++)
            s = s.replace("$" + i, Integer.toString(params[i]));

        return (s);
    }

    private static String[] internalMaterializeParamNames(String template, int level, Integer count0) {
        if (count0 == null || count0 == 0)
            return (new String[0]);

        if (!template.contains("$"))
            return (new String[]{template});

        String placeholder = "$" + level;

        String[] m0 = new String[count0];

        for (int i = 0; i < count0; i++)
            m0[i] = template.replace(placeholder, Integer.toString(i));

        Arrays.sort(m0);

        return (m0);
    }

    protected static List<String> materializeParamNames(String template, int level, Integer count0) {
        if (count0 == null || count0 == 0)
            return (new ArrayList<String>());

        return (
                Arrays.asList(
                        WxpayResponseBase.internalMaterializeParamNames(template, level, count0)
                )
        );
    }

    // EXCEPTION

    protected static List<String> materializeParamNames(String template, int level, Integer count0, List... counts) {
        if (count0 == null || count0 == 0)
            return (new ArrayList<String>());

        String[] m0 = WxpayResponseBase.internalMaterializeParamNames(template, level, count0);
        List<String> mx;

        if (!m0[0].contains("$")) {
            mx = Arrays.asList(m0);
        } else {
            mx = new ArrayList<String>();

            List<Integer> count1s = (List<Integer>) counts[0];

            if (counts.length == 1) {
                for (int i = 0; i < count0; i++) {
                    Integer count1 = count1s.get(i);
                    if (count1 == null)
                        continue;

                    mx.addAll(
                            materializeParamNames(m0[i], level + 1, count1)
                    );
                }
            } else {
                for (int i = 0; i < count0; i++) {
                    Integer count1 = count1s.get(i);
                    if (count1 == null)
                        continue;

                    List[] countsDesc = new List[counts.length - 1];
                    for (int j = 1; j < counts.length; j++)
                        countsDesc[j - 1] = (List) counts[j].get(i);

                    mx.addAll(
                            materializeParamNames(m0[i], level + 1, count1, countsDesc)
                    );
                }
            }
        }

        return (mx);
    }

    /**
     * retrieve callback params or response content as String
     *
     * @deprecated since 0.4.5 WxpayResponse no longer preserve the origin response body
     */
    public String getString() {
        return (this.respString);
    }

    /**
     * retrieve callback params or response content as Properties
     */
    public Properties getProperties() {
        return (this.respProp);
    }

    public final Integer getIntProperty(String key) {
        String v = this.getProperty(key);
        return (
                (v != null) ? Integer.valueOf(v) : null
        );
    }

    public final Date getDateProperty(String key) {
        try {
            String v = this.getProperty(key);
            return (
                    (v != null) ? new SimpleDateFormat("yyyyMMddHHmmss").parse(v) : null
            );
        } catch (Exception ex) {
            // rarely occurs
            ex.printStackTrace();
            return (null);
        }
    }

    // VERIFY

    /**
     * 此字段是通信标识，非交易标识，交易是否成功需要查看 result_code 来判断
     */
    public boolean isReturnCodeSuccess() {
        return (
                VALUE_SUCCESS.equals(this.getProperty(KEY_RETURN_CODE))
        );
    }

    public WxpayProtocolException getReturnMsg() {
        String returnMsg = this.getProperty(KEY_RETURN_MSG);

        if (returnMsg != null)
            return (new WxpayProtocolException(returnMsg));

        // else
        return (null);
    }

    public boolean isResultCodeSuccess() {
        return (
                VALUE_SUCCESS.equals(this.getProperty(KEY_RESULT_CODE))
        );
    }

    public WxpayException getErrCode() {
        String errCode = this.getProperty(KEY_ERR_CODE);

        if (errCode != null)
            return (new WxpayException(errCode));

        // else
        return (null);
    }

    public String getErrCodeDes() {
        return (
                this.getProperty(KEY_ERR_CODE_DES)
        );
    }

    /**
     * verify response sign
     *
     * @return true if passed (i.e. response content should be trusted), otherwise false
     */
    public boolean verify(Properties conf)
            throws UnsupportedEncodingException {
        if (this.validity != null)
            return (this.validity);

        Boolean skipSign = Boolean.valueOf(conf.getProperty("SKIP_VERIFY_SIGN"));

        // else
        this.validity = true;

        if (!skipSign)
            this.validity = this.validity && this.verifySign(conf);

        return (this.validity);
    }

    /**
     * 子类应该实现这个方法以验证签名
     * SUB-CLASS MUST IMPLEMENT THIS METHOD TO BE CALLBACKED. Default behavior do no verification, i.e. always return true.
     * A typical implemention should be <code>return(super.verifySign(this.keysParamName, conf)</code> where
     * );
     */
    protected boolean verifySign(Properties conf)
            throws UnsupportedEncodingException, UnsupportedOperationException {
        // DEFAULT IMPLEMENT
        return (true);
    }

    protected boolean verifySign(List<String> paramNames, Properties conf)
            throws UnsupportedEncodingException {
        this.respProp = buildConf(this.respProp, conf);
        String stated = this.getProperty("sign");
        String calculated = this.sign(
                paramNames
        );

        return (
                stated != null && stated.equals(calculated)
        );
    }

    /**
     * @param paramNames key names to submit, in dictionary order
     */
    protected String sign(List<String> paramNames)
            throws UnsupportedEncodingException, UnsupportedOperationException, IllegalStateException {
        String key = this.getProperty(KEY_KEY);

        String sign = this.signMD5(paramNames, key);

        return (sign);
    }
}

